// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "planetarypagelodjoint.h"
#include "planetaryscenemanager.h"
#include "planetaryoptions.h"
#include "planetaryheightmapdata.h"
#include "planetarypageheightmap.h"
#include "planetaryindexbuffermanager.h"
#include "planetarycommon.h"

//#define DEBUG_IF_STATEMENT (mRelation != NORTH || mParentZone != ZONE_UP)
#define DEBUG_IF_STATEMENT (1)

namespace Ogre
{
	String PlanetaryPageLODJoint::mType = "PlanetaryPageLODJoint";

	PlanetaryPageLODJoint::PlanetaryPageLODJoint(PlanetarySceneManager *mgr)
	{
		mSceneMgr = mgr;
		mOptions = mgr->getOptions();
		mPlanet = 0;
		mIsLoaded = false;
		mIsInit = false;
		mScale = mOptions->mPlanetRadius;
	}

	void PlanetaryPageLODJoint::init(unsigned int planet,unsigned int parentZone, unsigned int parentPageX,unsigned int parentPageZ, unsigned int neighbourZone, unsigned int neighbourPageX,unsigned int neighbourPageZ,NEIGHBOUR relation,SceneNode *pNode)
	{
		mPlanet = planet;
		mParentZone = parentZone;
		mParentPageX = parentPageX;
		mParentPageZ = parentPageZ;
		mNeighbourZone = neighbourZone;
		mNeighbourPageX = neighbourPageX;
		mNeighbourPageZ = neighbourPageZ;
		mSceneNode = pNode;
		mRelation = relation;

		mCastShadows = false;
		
		mName = "PlanetaryPageLODJoint::" + StringConverter::toString(parentZone) + "::" + StringConverter::toString(parentPageX) + "::" + StringConverter::toString(parentPageZ) + "::" + 
				StringConverter::toString(neighbourZone) + "::" + StringConverter::toString(neighbourPageX) + "::" + StringConverter::toString(neighbourPageZ);

		if(DEBUG_IF_STATEMENT)
			return;

		const unsigned int rowlength = mOptions->mPageSizeLOD1;
		const unsigned int rowlength2 = (mOptions->TileSize - 1) * mOptions->PageSize + 1;

		mMaterial = MaterialManager::getSingleton().getByName("DontCullMePlx");

		// Default query flags to top bit so users can exclude it if they wish
		MovableObject::mQueryFlags = SceneManager::WORLD_GEOMETRY_TYPE_MASK;
		MovableObject::setRenderQueueGroup(mSceneMgr->getWorldGeometryRenderQueue());

		mVertexes = new VertexData();
		mVertexes->vertexStart = 0;
		mVertexes->vertexCount = rowlength + rowlength2;

		VertexDeclaration *decl = mVertexes->vertexDeclaration;

		size_t offset = 0;
		decl->addElement(0,0,VET_FLOAT3,VES_POSITION);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT3, VES_NORMAL);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT2, VES_TEXTURE_COORDINATES);
		offset += VertexElement::getTypeSize(VET_FLOAT2);

		mIndexes = new IndexData();
		mIndexes->indexStart = 0;
		mIndexes->indexCount = mSceneMgr->getIndexBufferManager()->getIndexBufferSize(INDEX_BUFFER_PAGE_TO_TILE_JOINT);//3 * (rowlength2 - 1) + 3 * (rowlength - 1);

		mIsInit = true;
	}

	void PlanetaryPageLODJoint::load()
	{
		if(DEBUG_IF_STATEMENT)
			return;

		mBounds.setNull();

		VertexDeclaration *decl = mVertexes->vertexDeclaration;
		VertexBufferBinding *bind = mVertexes->vertexBufferBinding;

		// Setup vertices
		HardwareVertexBufferSharedPtr vbuf = 
			HardwareBufferManager::getSingleton().createVertexBuffer(
				decl->getVertexSize(0),
				mVertexes->vertexCount,
				HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		bind->setBinding(0,vbuf);

		const VertexElement* poselem = decl->findElementBySemantic(VES_POSITION);

		unsigned int vertexSize = vbuf->getVertexSize();

		uchar *pBuffer = (uchar*)vbuf->lock(HardwareBuffer::HBL_DISCARD);

		/// Insert vertices
		/// Check if the page and tile is in the same zone
		if(mParentZone == mNeighbourZone)
		{
			switch(mParentZone)
			{
				case ZONE_UP:
					loadParentUp(poselem,pBuffer,vertexSize);
					break;

				case ZONE_DOWN:
					loadParentDown(poselem,pBuffer,vertexSize);
					break;
				
				case ZONE_FRONT:
					loadParentFront(poselem,pBuffer,vertexSize);
					break;
				
				case ZONE_BACK:
					loadParentBack(poselem,pBuffer,vertexSize);
					break;
			
				case ZONE_LEFT:
					loadParentLeft(poselem,pBuffer,vertexSize);
					break;
				
				case ZONE_RIGHT:
					loadParentRight(poselem,pBuffer,vertexSize);
					break;
			}
			switch(mNeighbourZone)
			{
				case ZONE_UP:
					loadChildUp(poselem,pBuffer,vertexSize);
					break;

				case ZONE_DOWN:
					loadChildDown(poselem,pBuffer,vertexSize);
					break;
				
				case ZONE_FRONT:
					loadChildFront(poselem,pBuffer,vertexSize);
					break;
				
				case ZONE_BACK:
					loadChildBack(poselem,pBuffer,vertexSize);
					break;
			
				case ZONE_LEFT:
					loadChildLeft(poselem,pBuffer,vertexSize);
					break;
				
				case ZONE_RIGHT:
					loadChildRight(poselem,pBuffer,vertexSize);
					break;
			}
		}

		vbuf->unlock();

		// Setup indices
		mIndexes->indexBuffer = mSceneMgr->getIndexBufferManager()->getIndexBuffer(INDEX_BUFFER_PAGE_TO_TILE_JOINT);

		float x = (mBounds.getMaximum().x - mBounds.getMinimum().x);
		float y = (mBounds.getMaximum().y - mBounds.getMinimum().y);
		float z = (mBounds.getMaximum().y - mBounds.getMinimum().y);

		mCenter = Vector3(mBounds.getMinimum().x + (x/2.0f),mBounds.getMinimum().y + (y/2.0f),mBounds.getMinimum().z + (z/2.0f));

		mBoundingRadius = Math::Sqrt(Math::Sqr(x) + Math::Sqr(y) + Math::Sqr(z)) / 2.0f;

		mIsLoaded = true;
	}


	void PlanetaryPageLODJoint::loadParentUp(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();
		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mParentZone,mParentPageX,mParentPageZ,1);
		const float *fh;
		float *pPos;
		
		// Insert parent vertices first			
		for(unsigned int i = 0; i < mOptions->mPageSizeLOD1; i++)
		{
			float x,y,z;
			float hscale;
				
			switch(mRelation)
			{
				case NORTH:
					x = (-2.0f * static_cast<float>(i)/static_cast<float>(mOptions->mPageSizeLOD1 - 1)) + 1.0f;
					z = -1.0f;
					fh = heightmap->getRow(0);
					hscale = 1.0f + (((fh[(mOptions->mPageSizeLOD1 - 1) - i] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
				
				case SOUTH:
					x = (2.0f * static_cast<float>(i)/static_cast<float>(mOptions->mPageSizeLOD1 - 1)) - 1.0f;
					z = 1.0f;
					fh = heightmap->getRow(mOptions->mPageSizeLOD1 - 1);
					hscale = 1.0f + (((fh[i] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
					
				case EAST:
					x = 1.0f;
					z = (-2.0f * static_cast<float>(i)/static_cast<float>(mOptions->mPageSizeLOD1 - 1)) + 1.0f;
					fh = heightmap->getRow((mOptions->mPageSizeLOD1 - 1) - i);
					hscale = 1.0f + (((fh[mOptions->mPageSizeLOD1 - 1] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
					
				case WEST:
					x = -1.0f;
					z = (2.0f * static_cast<float>(i)/static_cast<float>(mOptions->mPageSizeLOD1 - 1)) - 1.0f;
					fh = heightmap->getRow(i);
					hscale = 1.0f + (((fh[0] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
			}

			y = 1.0f;

			//if(mRelation == EAST)
			//	LogManager::getSingletonPtr()->logMessage("Parent: (" + StringConverter::toString(x) + "," + StringConverter::toString(y) + "," + StringConverter::toString(z) + ")");

			Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;

			poselem->baseVertexPointerToElement(pBuffer, &pPos);

			mBounds.merge(pos);

			// Vertex coords
			*pPos++ = pos.x; // X
			*pPos++ = pos.y; // Y
			*pPos++ = pos.z; // Z

			// Normals
			Vector3 norm = pos.normalisedCopy();
			*pPos++ = norm.x; // X
			*pPos++ = norm.y; // Y
			*pPos++ = norm.z; // Z

			// Texture Coords
			//Vector2 tex(((x + 1.0f) / 2), ((z + 1.0f) / 2));
			Vector2 tex(static_cast<float>(i) / static_cast<float>(mOptions->mZoneSizeLOD1), 1.0f);
			*pPos++ = tex.x;
			*pPos++ = tex.y;

			pBuffer += vertexSize;
		}
	}

	void PlanetaryPageLODJoint::loadChildUp(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();
		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mNeighbourZone,mNeighbourPageX,mNeighbourPageZ);
		float *pPos;
		
		// Insert child vertices
		for(unsigned int j = 0; j < ((mOptions->TileSize - 1) * mOptions->PageSize + 1); j++)
		{
			float zoneSize = static_cast<float>(mOptions->ZoneSize * mOptions->PageSize * (mOptions->TileSize - 1));				
			float x,y,z;

			// TEMP:
			int i = 0;
			
			const float *fh;
			float hscale;
				
			switch(mRelation)
			{
				case NORTH:
					//x = (-2.0f * (static_cast<float>((i * (mOptions->mPageSizeLOD1 - 1)) + j) /
					//		zoneSize)) + 1.0f;
					//z = -1.0f;

					x = (2.0f * (static_cast<float>((mNeighbourPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + j) / zoneSize)) - 1.0f;
					z = ( 2.0f * (static_cast<float>((mNeighbourPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mOptions->PageSize * (mOptions->TileSize - 1))) /
						zoneSize)) - 1.0f;
					
					fh = heightmap->getRow((mOptions->PageSize * (mOptions->TileSize - 1)));
					hscale = 1.0f + (((fh[(mOptions->PageSize * (mOptions->TileSize - 1)) - j] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
				case SOUTH:
					x = (2.0f * (static_cast<float>((i * (mOptions->mPageSizeLOD1 - 1)) + j) /
							zoneSize)) - 1.0f;
					z = 1.0f;

					fh = heightmap->getRow(0,1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
				case EAST:
					x = 1.0f;
					z = (-2.0f * (static_cast<float>((i * (mOptions->mPageSizeLOD1 - 1)) + j) / 
						zoneSize)) + 1.0f;
					fh = heightmap->getRow(0,1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
				case WEST:
					x = -1.0f;
					z = ( 2.0f * (static_cast<float>((i * (mOptions->mPageSizeLOD1 - 1)) + j) /
							zoneSize)) - 1.0f;
					fh = heightmap->getRow(0,1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					break;
			}

			y = 1.0f;
			
			//if(mRelation == EAST)
			//	LogManager::getSingletonPtr()->logMessage("Child:   (" + StringConverter::toString(x) + "," + StringConverter::toString(y) + "," + StringConverter::toString(z) + ")");
		
			Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;
		
			poselem->baseVertexPointerToElement(pBuffer, &pPos);
		
			mBounds.merge(pos);
		
						// Vertex coords
			*pPos++ = pos.x; // X
			*pPos++ = pos.y; // Y
			*pPos++ = pos.z; // Z
		
						// Normals
			Vector3 norm = pos.normalisedCopy();
			*pPos++ = norm.x; // X
			*pPos++ = norm.y; // Y
			*pPos++ = norm.z; // Z
		
				// Texture Coords
			Vector2 tex(static_cast<float>(i) / static_cast<float>(mOptions->mZoneSizeLOD1), 1.0f);
			*pPos++ = tex.x;
			*pPos++ = tex.y;
		
			pBuffer += vertexSize ;
		}
	}
	void PlanetaryPageLODJoint::loadParentDown(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadChildDown(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadParentFront(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadChildFront(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadParentBack(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadChildBack(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadParentLeft(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadChildLeft(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadParentRight(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}
	void PlanetaryPageLODJoint::loadChildRight(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
	}

	void PlanetaryPageLODJoint::unload()
	{
		assert(mIsLoaded);

		mVertexes->vertexBufferBinding->unsetAllBindings();
		mIndexes->indexBuffer.setNull();

		mIsLoaded = false;
	}

	void PlanetaryPageLODJoint::uninit()
	{
		assert(mIsInit);

		mVertexes->vertexDeclaration->removeAllElements();
		delete mVertexes;
		mVertexes = NULL;

		delete mIndexes;
		mIndexes = NULL;

		mParentZone = 0;
		mNeighbourZone = 0;

		mIsInit = false;
	}

	void PlanetaryPageLODJoint::show(bool show)
	{
		if(DEBUG_IF_STATEMENT)
			return;

		if(show)
		{
			LogManager::getSingletonPtr()->logMessage("Showing " + zoneToStr(mParentZone) + "::" + StringConverter::toString(mParentPageX) + "::" + StringConverter::toString(mParentPageZ) + "::" + relationToStr(mRelation) + " joint");
			mVisible = true;
			mSceneNode->attachObject(this);
		}
		else
		{
			LogManager::getSingletonPtr()->logMessage("Hiding " + zoneToStr(mParentZone) + "::" + StringConverter::toString(mParentPageX) + "::" + StringConverter::toString(mParentPageZ) + "::" + relationToStr(mRelation) + " joint");
			mVisible = false;
			mSceneNode->detachObject(this);
		}
	}

	void PlanetaryPageLODJoint::getRenderOperation(RenderOperation& op)
	{
		op.useIndexes = true;
		op.operationType = RenderOperation::OT_TRIANGLE_LIST;
		op.vertexData = mVertexes;
		op.indexData = mIndexes;
	}

	void PlanetaryPageLODJoint::getWorldTransforms(Matrix4 *xform) const
	{
		*xform = mParentNode->_getFullTransform();
	}

	const Quaternion &PlanetaryPageLODJoint::getWorldOrientation() const
	{
		return mParentNode->_getDerivedOrientation();
	}

	const Vector3 &PlanetaryPageLODJoint::getWorldPosition() const
	{
		return mCenter;
	}
	
	Real PlanetaryPageLODJoint::getSquaredViewDepth(const Camera *cam) const
	{
		return (PlanetaryPageLODJoint::getWorldPosition() - cam->getDerivedPosition()).squaredLength();
	}

	const LightList &PlanetaryPageLODJoint::getLights() const
	{
		if (mLightListDirty)
		{
			getParentSceneNode()->getCreator()->_populateLightList(
					mCenter, 
					this->getBoundingRadius(), 
					mLightList);
					mLightListDirty = false;
		}
		return mLightList;
	}

	void PlanetaryPageLODJoint::_notifyCurrentCamera(Camera* cam)
	{
		if(!mIsLoaded || !mIsInit)
		{
			mVisible = false;
			return;
		}

		mVisible = true;
	}

	void PlanetaryPageLODJoint::_updateRenderQueue(RenderQueue* queue)
	{
		queue->addRenderable(this);
	}

	void PlanetaryPageLODJoint::_notifyAttached(Node* parent, bool isTagPoint)
	{
		MovableObject::_notifyAttached(parent, isTagPoint);
	}
}
